/* Copyright (c) 2012, 2014, Oracle and/or its affiliates. All rights reserved.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA */

#ifndef _SP_INSTR_H_
#define _SP_INSTR_H_

#include "my_global.h"    // NO_EMBEDDED_ACCESS_CHECKS
#include "sp_pcontext.h"  // sp_pcontext
#include "sql_class.h"    // THD
#include "sp_head.h"      // sp_printable

///////////////////////////////////////////////////////////////////////////
// This file contains SP-instruction classes.
///////////////////////////////////////////////////////////////////////////

/**
  An interface for all SP-instructions with destinations that
  need to be updated by the SP-optimizer.
*/
class sp_branch_instr
{
public:
  /**
    Update the destination; used by the SP-instruction-optimizer.

    @param old_dest current (old) destination (instruction pointer).
    @param new_dest new destination (instruction pointer).
  */
  virtual void set_destination(uint old_dest, uint new_dest) = 0;

  /**
    Update all instruction with the given label in the backpatch list to
    the specified instruction pointer.

    @param dest     destination instruction pointer.
  */
  virtual void backpatch(uint dest) = 0;

  virtual ~sp_branch_instr()
  { }
};

///////////////////////////////////////////////////////////////////////////

/**
  Base class for every SP-instruction. sp_instr defines interface and provides
  base implementation.
*/
class sp_instr : public Query_arena,
                 public Sql_alloc
{
public:
  sp_instr(uint ip, sp_pcontext *ctx)
   :Query_arena(0, STMT_INITIALIZED_FOR_SP),
    m_marked(false),
    m_ip(ip),
    m_parsing_ctx(ctx)
  { }

  virtual ~sp_instr()
  { free_items(); }

  uint get_ip() const
  { return m_ip; }

  /**
    Get the continuation destination (instruction pointer for the CONTINUE
    HANDLER) of this instruction.
    @return the continuation destination
  */
  virtual uint get_cont_dest() const
  { return get_ip() + 1; }

  sp_pcontext *get_parsing_ctx() const
  { return m_parsing_ctx; }

  virtual SQL_I_List<Item_trigger_field>* get_instr_trig_field_list()
  { return NULL; }

protected:
  /// Show if this instruction is reachable within the SP
  /// (used by SP-optimizer).
  bool m_marked;

  /// Instruction pointer.
  uint m_ip;

  /// Instruction parsing context.
  sp_pcontext *m_parsing_ctx;

private:
  // Prevent use of copy constructor and assignment operator.
  sp_instr(const sp_instr &);
  void operator= (sp_instr &);
};

///////////////////////////////////////////////////////////////////////////

/**
  sp_lex_instr is a class providing the interface and base implementation
  for SP-instructions, whose execution is based on expression evaluation.

  sp_lex_instr keeps LEX-object to be able to evaluate the expression.

  sp_lex_instr also provides possibility to re-parse the original query
  string if for some reason the LEX-object is not valid any longer.
*/
class sp_lex_instr : public sp_instr
{
public:
  sp_lex_instr(uint ip, sp_pcontext *ctx, LEX *lex, bool is_lex_owner)
   :sp_instr(ip, ctx),
    m_lex(NULL),
    m_is_lex_owner(false),
    m_first_execution(true),
    m_prelocking_tables(NULL),
    m_lex_query_tables_own_last(NULL)
  {
    memset(&m_lex_mem_root, 0, sizeof (MEM_ROOT));
  }

  virtual ~sp_lex_instr()
  {
    /*
      If the instruction is reparsed, m_lex_mem_root was used to allocate
      the items, then freeing the memroot, frees the items. Also free the
      items allocated on heap as well.
    */
    if (alloc_root_inited(&m_lex_mem_root))
      free_items();
    free_root(&m_lex_mem_root, MYF(0));
  }

  virtual SQL_I_List<Item_trigger_field>* get_instr_trig_field_list()
  { return &m_trig_field_list; }

private:
  /** 
    Mem-root for storing the LEX-tree during reparse. This
    mem-root is freed when a reparse is triggered or the stored
    routine is dropped.
  */
  MEM_ROOT m_lex_mem_root;

  /// LEX-object.
  LEX *m_lex;

  /**
    Indicates whether this sp_lex_instr instance is responsible for
    LEX-object deletion.
  */
  bool m_is_lex_owner;

  /**
    Indicates whether exec_core() has not been already called on the current
    LEX-object.
  */
  bool m_first_execution;

  /*****************************************************************************
    Support for being able to execute this statement in two modes:
    a) inside prelocked mode set by the calling procedure or its ancestor.
    b) outside of prelocked mode, when this statement enters/leaves
       prelocked mode itself.
  *****************************************************************************/

  /**
    List of additional tables this statement needs to lock when it
    enters/leaves prelocked mode on its own.
  */
  TABLE_LIST *m_prelocking_tables;

  /**
    The value m_lex->query_tables_own_last should be set to this when the
    statement enters/leaves prelocked mode on its own.
  */
  TABLE_LIST **m_lex_query_tables_own_last;

  /**
    List of all the Item_trigger_field's of instruction.
  */
  SQL_I_List<Item_trigger_field> m_trig_field_list;
};

///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_stmt represents almost all conventional SQL-statements, which are
  supported outside stored programs.

  SET-statements, which deal with SP-variable or NEW/OLD trigger pseudo-rows are
  not represented by this instruction.
*/
class sp_instr_stmt : public sp_lex_instr
{
public:
  sp_instr_stmt(uint ip,
                LEX *lex,
                LEX_STRING query)
   :sp_lex_instr(ip, lex->get_sp_current_parsing_ctx(), lex, true),
    m_query(query),
    m_valid(true)
  { }

  virtual bool is_invalid() const
  { return !m_valid; }

  virtual void invalidate()
  { m_valid= false; }

  virtual void get_query(String *sql_query) const
  { sql_query->append(m_query.str, m_query.length); }

  virtual bool on_after_expr_parsing(THD *thd)
  {
    m_valid= true;
    return false;
  }

private:
  /// Complete query of the SQL-statement.
  LEX_STRING m_query;

  /// Specify if the stored LEX-object is up-to-date.
  bool m_valid;
};

///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_set represents SET-statememnts, which deal with SP-variables.
*/
class sp_instr_set : public sp_lex_instr
{
public:
  sp_instr_set(uint ip,
               LEX *lex,
	       uint offset,
               Item *value_item,
               LEX_STRING value_query,
               bool is_lex_owner)
   :sp_lex_instr(ip, lex->get_sp_current_parsing_ctx(), lex, is_lex_owner),
    m_offset(offset),
    m_value_item(value_item),
    m_value_query(value_query)
  { }

  virtual bool is_invalid() const
  { return m_value_item == NULL; }

  virtual void invalidate()
  { m_value_item= NULL; }
  virtual LEX_STRING get_expr_query() const
  { return m_value_query; }

private:
  /// Frame offset.
  uint m_offset;

  /// Value expression item of the SET-statement.
  Item *m_value_item;

  /// SQL-query corresponding to the value expression.
  LEX_STRING m_value_query;
};

///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_set_trigger_field represents SET-statements, which deal with NEW/OLD
  trigger pseudo-rows.
*/
class sp_instr_set_trigger_field : public sp_lex_instr
{
public:
  sp_instr_set_trigger_field(uint ip,
                             LEX *lex,
                             LEX_STRING trigger_field_name,
                             Item_trigger_field *trigger_field,
                             Item *value_item,
                             LEX_STRING value_query)
   :sp_lex_instr(ip, lex->get_sp_current_parsing_ctx(), lex, true),
    m_trigger_field_name(trigger_field_name),
    m_trigger_field(trigger_field),
    m_value_item(value_item),
    m_value_query(value_query)
  { }



  virtual bool is_invalid() const
  { return m_value_item == NULL; }

  virtual void invalidate()
  { m_value_item= NULL; }

  virtual LEX_STRING get_expr_query() const
  { return m_value_query; }

private:
  /// Trigger field name ("field_name" of the "NEW.field_name").
  LEX_STRING m_trigger_field_name;

  /// Item corresponding to the NEW/OLD trigger field.
  Item_trigger_field *m_trigger_field;

  /// Value expression item of the SET-statement.
  Item *m_value_item;

  /// SQL-query corresponding to the value expression.
  LEX_STRING m_value_query;
};

///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_freturn represents RETURN statement in stored functions.
*/
class sp_instr_freturn : public sp_lex_instr
{
public:
  sp_instr_freturn(uint ip,
                   LEX *lex,
		   Item *expr_item,
                   LEX_STRING expr_query,
                   enum enum_field_types return_field_type)
   :sp_lex_instr(ip, lex->get_sp_current_parsing_ctx(), lex, true),
    m_expr_item(expr_item),
    m_expr_query(expr_query),
    m_return_field_type(return_field_type)
  { }


  /////////////////////////////////////////////////////////////////////////
  // sp_instr implementation.
  /////////////////////////////////////////////////////////////////////////
  virtual bool is_invalid() const
  { return m_expr_item == NULL; }

  virtual void invalidate()
  {
    // it's already deleted.
    m_expr_item= NULL;
  }

  virtual LEX_STRING get_expr_query() const
  { return m_expr_query; }

private:
  /// RETURN-expression item.
  Item *m_expr_item;

  /// SQL-query corresponding to the RETURN-expression.
  LEX_STRING m_expr_query;

  /// RETURN-field type code.
  enum enum_field_types m_return_field_type;
};

///////////////////////////////////////////////////////////////////////////

/**
  This is base class for all kinds of jump instructions.

  @note this is the only class, we directly construct instances of, that has
  subclasses. We also redefine sp_instr_jump behavior in those subclasses.

  @todo later we will consider introducing a new class, which will be the base
  for sp_instr_jump, sp_instr_set_case_expr and sp_instr_jump_case_when.
  Something like sp_regular_branch_instr (similar to sp_lex_branch_instr).
*/
class sp_instr_jump : public sp_instr,
                      public sp_branch_instr
{
public:
  sp_instr_jump(uint ip, sp_pcontext *ctx)
   :sp_instr(ip, ctx),
    m_dest(0)
  { }

  sp_instr_jump(uint ip, sp_pcontext *ctx, uint dest)
   :sp_instr(ip, ctx),
    m_dest(dest)
  { }

  /////////////////////////////////////////////////////////////////////////
  // sp_branch_instr implementation.
  /////////////////////////////////////////////////////////////////////////

  virtual void set_destination(uint old_dest, uint new_dest)
  {
    if (m_dest == old_dest)
      m_dest= new_dest;
  }

  virtual void backpatch(uint dest)
  {
    /* Calling backpatch twice is a logic flaw in jump resolution. */
    DBUG_ASSERT(m_dest == 0);
    m_dest= dest;
  }

protected:
  /// Where we will go.
  uint m_dest;
};

///////////////////////////////////////////////////////////////////////////

/**
  sp_lex_branch_instr is a base class for SP-instructions, which might perform
  conditional jump depending on the value of an SQL-expression.
*/
class sp_lex_branch_instr : public sp_lex_instr,
                            public sp_branch_instr
{
protected:
  sp_lex_branch_instr(uint ip, sp_pcontext *ctx, LEX *lex,
                      Item *expr_item, LEX_STRING expr_query)
   :sp_lex_instr(ip, ctx, lex, true),
    m_dest(0),
    m_cont_dest(0),
    m_optdest(NULL),
    m_cont_optdest(NULL),
    m_expr_item(expr_item),
    m_expr_query(expr_query)
  { }

  sp_lex_branch_instr(uint ip, sp_pcontext *ctx, LEX *lex,
                      Item *expr_item, LEX_STRING expr_query,
                      uint dest)
   :sp_lex_instr(ip, ctx, lex, true),
    m_dest(dest),
    m_cont_dest(0),
    m_optdest(NULL),
    m_cont_optdest(NULL),
    m_expr_item(expr_item),
    m_expr_query(expr_query)
  { }

public:
  void set_cont_dest(uint cont_dest)
  { m_cont_dest= cont_dest; }

  virtual uint get_cont_dest() const
  { return m_cont_dest; }

  /////////////////////////////////////////////////////////////////////////
  // sp_lex_instr implementation.
  /////////////////////////////////////////////////////////////////////////

  virtual bool is_invalid() const
  { return m_expr_item == NULL; }

  virtual void invalidate()
  { m_expr_item= NULL; /* it's already deleted. */ }

  virtual LEX_STRING get_expr_query() const
  { return m_expr_query; }

  /////////////////////////////////////////////////////////////////////////
  // sp_branch_instr implementation.
  /////////////////////////////////////////////////////////////////////////

  virtual void set_destination(uint old_dest, uint new_dest)
  {
    if (m_dest == old_dest)
      m_dest= new_dest;

    if (m_cont_dest == old_dest)
      m_cont_dest= new_dest;
  }

  virtual void backpatch(uint dest)
  {
    /* Calling backpatch twice is a logic flaw in jump resolution. */
    DBUG_ASSERT(m_dest == 0);
    m_dest= dest;
  }

protected:
  /// Where we will go.
  uint m_dest;

  /// Where continue handlers will go.
  uint m_cont_dest;

  // The following attributes are used by SP-optimizer.
  sp_instr *m_optdest;
  sp_instr *m_cont_optdest;

  /// Expression item.
  Item *m_expr_item;

  /// SQL-query corresponding to the expression.
  LEX_STRING m_expr_query;
};

///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_jump_if_not implements SP-instruction, which does the jump if its
  SQL-expression is false.
*/
class sp_instr_jump_if_not : public sp_lex_branch_instr
{
public:
  sp_instr_jump_if_not(uint ip,
                       LEX *lex,
                       Item *expr_item,
                       LEX_STRING expr_query)
   :sp_lex_branch_instr(ip, lex->get_sp_current_parsing_ctx(), lex,
                        expr_item, expr_query)
  { }

  sp_instr_jump_if_not(uint ip,
                       LEX *lex,
                       Item *expr_item,
                       LEX_STRING expr_query,
                       uint dest)
   :sp_lex_branch_instr(ip, lex->get_sp_current_parsing_ctx(), lex,
                        expr_item, expr_query, dest)
  { }


  virtual bool on_after_expr_parsing(THD *thd)
  {
    DBUG_ASSERT(thd->lex->select_lex.item_list.elements == 1);

    m_expr_item= thd->lex->select_lex.item_list.head();

    return false;
  }
};

///////////////////////////////////////////////////////////////////////////
// Instructions used for the "simple CASE" implementation.
///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_set_case_expr is used in the "simple CASE" implementation to evaluate
  and store the CASE-expression in the runtime context.
*/
class sp_instr_set_case_expr : public sp_lex_branch_instr
{
public:
  sp_instr_set_case_expr(uint ip,
                         LEX *lex,
                         uint case_expr_id,
                         Item *case_expr_item,
                         LEX_STRING case_expr_query)
   :sp_lex_branch_instr(ip, lex->get_sp_current_parsing_ctx(), lex,
                        case_expr_item, case_expr_query),
    m_case_expr_id(case_expr_id)
  { }


  /////////////////////////////////////////////////////////////////////////
  // sp_branch_instr implementation.
  /////////////////////////////////////////////////////////////////////////

  /*
    NOTE: set_destination() and backpatch() are overriden here just because the
    m_dest attribute is not used by this class, so there is no need to do
    anything about it.

    @todo These operations probably should be left as they are (i.e. do not
    override them here). The m_dest attribute would be set and not used, but
    that should not be a big deal.

    @todo This also indicates deficiency of the current SP-istruction class
    hierarchy.
  */

  virtual void set_destination(uint old_dest, uint new_dest)
  {
    if (m_cont_dest == old_dest)
      m_cont_dest= new_dest;
  }

  virtual void backpatch(uint dest)
  { }

private:
  /// Identifier (index) of the CASE-expression in the runtime context.
  uint m_case_expr_id;
};

///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_jump_case_when instruction is used in the "simple CASE"
  implementation. It's a jump instruction with the following condition:
    (CASE-expression = WHEN-expression)
  CASE-expression is retrieved from sp_rcontext;
  WHEN-expression is kept by this instruction.
*/
class sp_instr_jump_case_when : public sp_lex_branch_instr
{
public:
  sp_instr_jump_case_when(uint ip,
                          LEX *lex,
                          int case_expr_id,
                          Item *when_expr_item,
                          LEX_STRING when_expr_query)
   :sp_lex_branch_instr(ip, lex->get_sp_current_parsing_ctx(), lex,
                        when_expr_item, when_expr_query),
    m_case_expr_id(case_expr_id)
  { }

  virtual void invalidate()
  {
    // Items should be already deleted in lex-keeper.
    m_case_expr_item= NULL;
    m_eq_item= NULL;
    m_expr_item= NULL; // it's a WHEN-expression.
  }

  virtual bool on_after_expr_parsing(THD *thd)
  { return build_expr_items(thd); }

private:
  /**
    Build CASE-expression item tree:
      Item_func_eq(case-expression, when-i-expression)

    This function is used for the following form of CASE statement:
      CASE case-expression
        WHEN when-1-expression THEN ...
        WHEN when-2-expression THEN ...
        ...
        WHEN when-n-expression THEN ...
      END CASE

    The thing is that after the parsing we have an item (item tree) for the
    case-expression and for each when-expression. Here we build jump
    conditions: expressions like (case-expression = when-i-expression).

    @param thd  Thread context.

    @return Error flag.
  */
  bool build_expr_items(THD *thd)
  {
      // Setup CASE-expression item (m_case_expr_item).

      m_case_expr_item= new Item_case_expr(m_case_expr_id);

      if (!m_case_expr_item)
          return true;

#ifndef DBUG_OFF
      m_case_expr_item->m_sp= thd->lex->sphead;
#endif

      // Setup WHEN-expression item (m_expr_item) if it is not already set.
      //
      // This function can be called in two cases:
      //
      //   - during initial (regular) parsing of SP. In this case we don't have
      //     lex->select_lex (because it's not a SELECT statement), but
      //     m_expr_item is already set in constructor.
      //
      //   - during re-parsing after meta-data change. In this case we've just
      //     parsed aux-SELECT statement, so we need to take 1st (and the only one)
      //     item from its list.

      if (!m_expr_item)
      {
          DBUG_ASSERT(thd->lex->select_lex.item_list.elements == 1);

          m_expr_item= thd->lex->select_lex.item_list.head();
      }

      // Setup main expression item (m_expr_item).

      m_eq_item= new Item_func_eq(m_case_expr_item, m_expr_item);

      if (!m_eq_item)
          return true;

      return false;
  }

private:
  /// Identifier (index) of the CASE-expression in the runtime context.
  int m_case_expr_id;

  /// Item representing the CASE-expression.
  Item_case_expr *m_case_expr_item;

  /**
    Item corresponding to the main item of the jump-condition-expression:
    it's the equal function (=) in the (case-expression = when-i-expression)
    expression.
  */
  Item *m_eq_item;
};

///////////////////////////////////////////////////////////////////////////
// SQL-condition handler instructions.
///////////////////////////////////////////////////////////////////////////

class sp_instr_hpush_jump : public sp_instr_jump
{
public:
  sp_instr_hpush_jump(uint ip,
                      sp_pcontext *ctx,
                      sp_handler *handler)
   :sp_instr_jump(ip, ctx),
    m_handler(handler),
    m_opt_hpop(0),
    m_frame(ctx->current_var_count())
  {
    DBUG_ASSERT(m_handler->condition_values.elements == 0);
  }

  virtual ~sp_instr_hpush_jump()
  {
    m_handler->condition_values.empty();
    m_handler= NULL;
  }

  void add_condition(sp_condition_value *condition_value)
  { m_handler->condition_values.push_back(condition_value); }

  sp_handler *get_handler()
  { return m_handler; }

  /////////////////////////////////////////////////////////////////////////
  // sp_branch_instr implementation.
  /////////////////////////////////////////////////////////////////////////

  virtual void backpatch(uint dest)
  {
    DBUG_ASSERT(!m_dest || !m_opt_hpop);
    if (!m_dest)
      m_dest= dest;
    else
      m_opt_hpop= dest;
  }

private:
  /// Handler.
  sp_handler *m_handler;

  /// hpop marking end of handler scope.
  uint m_opt_hpop;

  // This attribute is needed for SHOW PROCEDURE CODE only (i.e. it's needed in
  // debug version only). It's used in print().
  uint m_frame;
};

///////////////////////////////////////////////////////////////////////////

class sp_instr_hpop : public sp_instr
{
public:
  sp_instr_hpop(uint ip, sp_pcontext *ctx)
    : sp_instr(ip, ctx)
  { }

  /////////////////////////////////////////////////////////////////////////
  // sp_printable implementation.
  /////////////////////////////////////////////////////////////////////////

  virtual void print(String *str)
  { str->append(STRING_WITH_LEN("hpop")); }
};

///////////////////////////////////////////////////////////////////////////

class sp_instr_hreturn : public sp_instr_jump
{
public:
  sp_instr_hreturn(uint ip, sp_pcontext *ctx)
   :sp_instr_jump(ip, ctx),
    m_frame(ctx->current_var_count())
  { }

private:
  // This attribute is needed for SHOW PROCEDURE CODE only (i.e. it's needed in
  // debug version only). It's used in print().
  uint m_frame;
};

///////////////////////////////////////////////////////////////////////////
// Cursor implementation.
///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_cpush corresponds to DECLARE CURSOR, implements DECLARE CURSOR and
  OPEN.

  This is the most important instruction in cursor implementation. It is created
  and added to sp_head when DECLARE CURSOR is being parsed. The arena of this
  instruction contains LEX-object for the cursor's SELECT-statement.

  This instruction is actually used to open the cursor.

  execute() operation "implements" DECLARE CURSOR statement -- it merely pushes
  a new cursor object into the stack in sp_rcontext object.

  exec_core() operation implements OPEN statement. It is important to implement
  OPEN statement in this instruction, because OPEN may lead to re-parsing of the
  SELECT-statement. So, the original Arena and parsing context must be used.
*/
class sp_instr_cpush : public sp_lex_instr
{
public:
  sp_instr_cpush(uint ip,
                 sp_pcontext *ctx,
                 LEX *cursor_lex,
                 LEX_STRING cursor_query,
                 int cursor_idx)
   :sp_lex_instr(ip, ctx, cursor_lex, true),
    m_cursor_query(cursor_query),
    m_valid(true),
    m_cursor_idx(cursor_idx)
  {
    // Cursor can't be stored in Query Cache, so we should prevent opening QC
    // for try to write results which are absent.

    cursor_lex->safe_to_cache_query= false;
  }

  /////////////////////////////////////////////////////////////////////////
  // Query_arena implementation.
  /////////////////////////////////////////////////////////////////////////

  /**
    This call is used to cleanup the instruction when a sensitive
    cursor is closed. For now stored procedures always use materialized
    cursors and the call is not used.
  */
  virtual void cleanup_stmt()
  { /* no op */ }

  virtual bool is_invalid() const
  { return !m_valid; }

  virtual void invalidate()
  { m_valid= false; }

  virtual void get_query(String *sql_query) const
  { sql_query->append(m_cursor_query.str, m_cursor_query.length); }

  virtual bool on_after_expr_parsing(THD *thd)
  {
    m_valid= true;
    return false;
  }

private:
  /// This attribute keeps the cursor SELECT statement.
  LEX_STRING m_cursor_query;

  /// Flag if the LEX-object of this instruction is valid or not.
  /// The LEX-object is not valid when metadata have changed.
  bool m_valid;

  /// Used to identify the cursor in the sp_rcontext.
  int m_cursor_idx;
};

///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_cpop instruction is added at the end of BEGIN..END block.
  It's used to remove declared cursors so that they are not visible any longer.
*/
class sp_instr_cpop : public sp_instr
{
public:
  sp_instr_cpop(uint ip, sp_pcontext *ctx, uint count)
   :sp_instr(ip, ctx),
    m_count(count)
  { }

private:
  uint m_count;
};

///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_copen represents OPEN statement (opens the cursor).
  However, the actual implementation is in sp_instr_cpush::exec_core().
*/
class sp_instr_copen : public sp_instr
{
public:
  sp_instr_copen(uint ip, sp_pcontext *ctx, int cursor_idx)
   :sp_instr(ip, ctx),
    m_cursor_idx(cursor_idx)
  { }

private:
  /// Used to identify the cursor in the sp_rcontext.
  int m_cursor_idx;
};

///////////////////////////////////////////////////////////////////////////

/**
  The instruction corresponds to the CLOSE statement.
  It just forwards the close-call to the appropriate sp_cursor object in the
  sp_rcontext.
*/
class sp_instr_cclose : public sp_instr
{
public:
  sp_instr_cclose(uint ip, sp_pcontext *ctx, int cursor_idx)
   :sp_instr(ip, ctx),
    m_cursor_idx(cursor_idx)
  { }

private:
  /// Used to identify the cursor in the sp_rcontext.
  int m_cursor_idx;
};

///////////////////////////////////////////////////////////////////////////

/**
  The instruction corresponds to the FETCH statement.
  It just forwards the close-call to the appropriate sp_cursor object in the
  sp_rcontext.
*/
class sp_instr_cfetch : public sp_instr
{
public:
  sp_instr_cfetch(uint ip, sp_pcontext *ctx, int cursor_idx)
   :sp_instr(ip, ctx),
    m_cursor_idx(cursor_idx)
  { }

  void add_to_varlist(sp_variable *var)
  { m_varlist.push_back(var); }

private:
  /// List of SP-variables to store fetched values.
  List<sp_variable> m_varlist;

  /// Used to identify the cursor in the sp_rcontext.
  int m_cursor_idx;
};

///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////

/**
  sp_instr_error just throws an SQL-condition if the execution flow comes to it.
  It's used in the CASE implementation to perform runtime-check that the
  CASE-expression is handled by some WHEN/ELSE clause.
*/
class sp_instr_error : public sp_instr
{
public:
  sp_instr_error(uint ip, sp_pcontext *ctx, int errcode)
   :sp_instr(ip, ctx),
    m_errcode(errcode)
  { }

private:
  /// The error code, which should be raised by this instruction.
  int m_errcode;
};

///////////////////////////////////////////////////////////////////////////

#endif // _SP_INSTR_H_
